#!/usr/bin/python3
# -*- coding:utf-8 -*-

import numpy
import random


class BackPro:

	def __init__(self, input_layer_count, output_layer_count, hidden_layer_count=None):
		if hidden_layer_count is None or hidden_layer_count <= 0:
			hidden_layer_count = int(numpy.sqrt(input_layer_count + output_layer_count)) + 3
		self.input_layer_count = input_layer_count
		self.output_layer_count = output_layer_count
		self.hidden_layer_count = hidden_layer_count
		self.w1 = numpy.random.random((input_layer_count, hidden_layer_count))
		self.w2 = numpy.random.random((hidden_layer_count, output_layer_count))
		self.an = numpy.random.random(hidden_layer_count).reshape((1, hidden_layer_count))
		self.bn = numpy.random.random(output_layer_count).reshape((1, output_layer_count))
		self.learn_rate = .01
		self.stop_delta = .5

	def train(self, dataset):
		iter_count = 0
		dataset = tuple(filter(lambda item: item is not None, dataset))

		max_delta = 1.0
		while iter_count < 10000 and max_delta > self.stop_delta:
			max_delta, en = 0, None
			for sample in dataset:
				#正向传播
				hn = 1 / (1.0 + numpy.exp(-1 * (numpy.dot(sample[0], self.w1) + self.an)))
				on = numpy.dot(hn, self.w2) + self.bn
				en = numpy.subtract(sample[1], on)
				max_delta = max(max(numpy.abs(en[0])), max_delta)
				#反向计算
				for j in range(0, self.hidden_layer_count):
					for k in range(0, self.output_layer_count):
						delta = self.learn_rate * hn[0][j] * en[0][k]
						#max_delta = max(abs(delta), max_delta)
						#print(delta)
						self.w2[j][k] += delta

				self.bn = self.bn + self.learn_rate * en

				for i in range(0, self.input_layer_count):
					for j in range(0, self.hidden_layer_count):
						delta = self.learn_rate * hn[0][j] * (1-hn[0][j]) * sample[0][0][i] * sum([en[0][k] * self.w2[j][k] for k in range(0, self.output_layer_count)])
						#max_delta = max(abs(delta), max_delta)
						self.w1[i][j] += self.learn_rate * delta

				for i in range(0, self.hidden_layer_count):
					self.an[0][i] += self.learn_rate * hn[0][i] * (1-hn[0][i]) * sum([en[0][k] * self.w2[i][k] for k in range(0, self.output_layer_count)])

			iter_count += 1
			if iter_count % 500 == 0:
				print("delta:%s, w1=\n%s\nw2=\n%s\nen=%s" % (max_delta, self.w1, self.w2, en))

	def predict(self, sample):
		if sample is None:
			return "null"
		on = numpy.dot(1 / (1 + numpy.exp(-1 * (numpy.dot(sample, self.w1)  + self.an))), self.w2) + self.bn
		return on


	def __str__(self):
		return "BackPro learn rate:%s, w1=\n%s\nw2=\n%s\nan=\n%s\nbn=\n%s" % (
			self.learn_rate, self.w1, self.w2, self.an, self.bn)


def line_to_sample(line):
	items = line.strip().split(",")
	if len(items) < 5:
		return None
	return (numpy.array([items[:-1]], dtype=numpy.float64), numpy.array([
		{
			"Iris-setosa": (1.0, 0.0, 0.0),
			"Iris-versicolor": (0.0, 1.0, 0.0),
			"Iris-virginica": (0.0, 0.0, 1.0),
		}.get(items[-1])], dtype=numpy.float64))


def iris_train():
	with open("./iris.data") as file:
		all_data = file.readlines()
		ann = BackPro(4, 3)
		dataset = [line_to_sample(line) for line in filter(lambda line : random.randint(1, 100) > 70, all_data)]
		ann.train(dataset)
		return ann


def iris_predict(ann):
	success_count, total_count = 0, 0
	with open("./iris.data") as file:
		all_data = file.readlines()
		total_count = len(all_data)
		for line in all_data:
			sample = line_to_sample(line)
			if sample is None:
				continue
			format_data = ann.predict(sample[0]).tolist()[0]
			print("predict: %s, result:%s" % ([round(float(format_data[k]), 3) for k in range(0, ann.output_layer_count)], sample[1]))
			predict_max_index = format_data.index(max(format_data))
			result_max_index = tuple(sample[1][0]).index(max(sample[1][0]))
			success_count += (predict_max_index == result_max_index and 1 or 0)
	print("success rate:%s" % (success_count * 1.0 / total_count))


if __name__ == "__main__":
	iris_predict(iris_train())